import moment from 'moment'
import React from 'react'
import nzh from 'nzh/cn'
import { parse, stringify } from 'qs'
import CryptoJS from 'crypto-js'
import formatXml from 'xml-formatter'
import get from 'lodash/get'
import { trimStart } from 'lodash'
import FileSaver from 'file-saver'
import { SESSION_PREFIX } from '@/defaultSettings'
import request from '@/utils/request'

const _storageMode = ['sessionStorage', 'localStorage']

export function fixedZero(val) {
  return val * 1 < 10 ? `0${val}` : val
}

export function getTimeDistance(type) {
  const now = new Date()
  const oneDay = 1000 * 60 * 60 * 24

  if (type === 'today') {
    now.setHours(0)
    now.setMinutes(0)
    now.setSeconds(0)
    return [moment(now), moment(now.getTime() + (oneDay - 1000))]
  }

  if (type === 'week') {
    let day = now.getDay()
    now.setHours(0)
    now.setMinutes(0)
    now.setSeconds(0)

    if (day === 0)
      day = 6

    else
      day -= 1

    const beginTime = now.getTime() - day * oneDay

    return [moment(beginTime), moment(beginTime + (7 * oneDay - 1000))]
  }

  if (type === 'month') {
    const year = now.getFullYear()
    const month = now.getMonth()
    const nextDate = moment(now).add(1, 'months')
    const nextYear = nextDate.year()
    const nextMonth = nextDate.month()

    return [
      moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`),
      moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000),
    ]
  }

  const year = now.getFullYear()
  return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)]
}

export function getPlainNode(nodeList, parentPath = '') {
  const arr = []
  nodeList.forEach((node) => {
    const item = node
    item.path = `${parentPath}/${item.path || ''}`.replace(/\/+/g, '/')
    item.exact = true
    if (item.children && !item.component) {
      arr.push(...getPlainNode(item.children, item.path))
    }
    else {
      if (item.children && item.component)
        item.exact = false

      arr.push(item)
    }
  })
  return arr
}

export function digitUppercase(n) {
  return nzh.toMoney(n)
}

function getRelation(str1, str2) {
  if (str1 === str2)
    console.warn('Two path are equal!'); // eslint-disable-line

  const arr1 = str1.split('/')
  const arr2 = str2.split('/')
  if (arr2.every((item, index) => item === arr1[index]))
    return 1

  if (arr1.every((item, index) => item === arr2[index]))
    return 2

  return 3
}

function getRenderArr(routes) {
  let renderArr = []
  renderArr.push(routes[0])
  for (let i = 1; i < routes.length; i += 1) {
    // 去重
    renderArr = renderArr.filter(item => getRelation(item, routes[i]) !== 1)
    // 是否包含
    const isAdd = renderArr.every(item => getRelation(item, routes[i]) === 3)
    if (isAdd)
      renderArr.push(routes[i])
  }
  return renderArr
}

/**
 * Get router routing configuration
 * { path:{name,...param}}=>Array<{name,path ...param}>
 * @param {string} path
 * @param {routerData} routerData
 */
export function getRoutes(path, routerData) {
  let routes = Object.keys(routerData).filter(
    routePath => routePath.indexOf(path) === 0 && routePath !== path,
  )
  // Replace path to '' eg. path='user' /user/name => name
  routes = routes.map(item => item.replace(path, ''))
  // Get the route to be rendered to remove the deep rendering
  const renderArr = getRenderArr(routes)
  // Conversion and stitching parameters
  const renderRoutes = renderArr.map((item) => {
    const exact = !routes.some(route => route !== item && getRelation(route, item) === 1)
    return {
      exact,
      ...routerData[`${path}${item}`],
      key: `${path}${item}`,
      path: `${path}${item}`,
    }
  })
  return renderRoutes
}

export function getPageQuery() {
  return parse(window.location.href.split('?')[1])
}

export function getQueryPath(path = '', query = {}) {
  const search = stringify(query)
  if (search.length)
    return `${path}?${search}`

  return path
}

/* eslint no-useless-escape:0 */
const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/

export function isUrl(path) {
  return reg.test(path)
}

export function formatWan(val) {
  const v = val * 1
  if (!v || Number.isNaN(v))
    return ''

  let result = val
  if (val > 10000) {
    result = Math.floor(val / 10000)
    result = (
      <span>
        {result}
        <span
          styles={{
            position: 'relative',
            top: -2,
            fontSize: 14,
            fontStyle: 'normal',
            lineHeight: 20,
            marginLeft: 2,
          }}
        >
          万
        </span>
      </span>
    )
  }
  return result
}

/**
 * 判断是否是整数
 * @param {String} str
 */
export function isInt(str) {
  str = str.replace(/^0+/, '') || '0'
  const n = Math.floor(Number(str))
  return n !== Infinity && String(n) === str && n >= 0
}

/**
 * 截取字符串最后一个"点"后面的内容
 * @param {String} str
 */
export function afterLastDot(str) {
  return str.substr(str.lastIndexOf('.') + 1)
}

/**
 * 格式化json字符串
 * @param {String} json
 */
export function formatJSON(json, tabSize) {
  if (typeof json === 'string') {
    try {
      const jsonObject = JSON.parse(json)
      return formatJSON(jsonObject, tabSize)
    }
    catch (e) {
      return json
    }
  }
  return JSON.stringify(json, null, tabSize || 3)
}

/**
 * 格式化xml字符串
 * @param {String} xml
 */
export function formatXML(xml) {
  // return xml;
  try {
    return formatXml(xml, {
      indentation: '   ',
      collapseContent: true,
    })
  }
  catch (e) {
    return xml
  }
}

/**
 * 压缩json字符串
 * @param {String} json
 */
export function compactJSON(json) {
  return JSON.stringify(JSON.parse(json))
}

/**
 * 判断是否JSON字符串
 * @param {String} str
 */
export function isJSON(str) {
  if (typeof str === 'string') {
    try {
      const obj = JSON.parse(str)
      if (typeof obj === 'object' && obj)
        return true

      return false
    }
    catch (e) {
      return false
    }
  }
  return false
}

/**
 * 判断数组是否有多于一个元素
 * @param {Array} array
 */
export function hasMoreThanOne(array) {
  return Array.isArray(array) && array.length >= 1
}

/**
 * IE浏览器检测
 *
 * 返回值     值类型    值说明
 *    -1      Number    不是ie浏览器
 *    6       Number    ie版本<=6
 *    7       Number    ie7
 *    8       Number    ie8
 *    9       Number    ie9
 *   10       Number    ie10
 *   11       Number    ie11
 *  'edge'    String    ie的edge浏览器
 */
export function IEVersion() {
  const { userAgent } = navigator // 取得浏览器的userAgent字符串
  const isIE = userAgent.includes('compatible') && userAgent.includes('MSIE') // 判断是否IE<11浏览器
  const isEdge = userAgent.includes('Edge') && !isIE // 判断是否IE的Edge浏览器
  const isIE11 = userAgent.includes('Trident') && userAgent.includes('rv:11.0')
  if (isIE) {
    const fIEVersion = userAgent.match(/MSIE (\d+)/)[1]
    if (fIEVersion === '7')
      return 7

    if (fIEVersion === '8')
      return 8

    if (fIEVersion === '9')
      return 9

    if (fIEVersion === '10')
      return 10

    return 6 // IE版本<=7
  }
  if (isEdge)
    return 'edge' // edge

  if (isIE11)
    return 11 // IE11

  return -1 // 不是ie浏览器
}

const _fixPlaceholderForIE = function () {
  const isIE8 = IEVersion() === 8
  const isIE9 = IEVersion() === 9
  // fix html5 placeholder attribute for ie7 & ie8
  if (isIE8 || isIE9) {
    // ie8 & ie9
    // this is html5 placeholder fix for inputs, inputs with placeholder-no-fix class will be skipped(e.g: we need this for password fields)
    $(
      'input[placeholder]:not(.placeholder-no-fix), textarea[placeholder]:not(.placeholder-no-fix)',
    ).each(function () {
      const input = $(this)

      if (input.val() === '' && input.attr('placeholder') !== '')
        input.addClass('placeholder').val(input.attr('placeholder'))

      input.focus(() => {
        if (input.val() === input.attr('placeholder'))
          input.val('')
      })

      input.blur(() => {
        if (input.val() === '' || input.val() === input.attr('placeholder'))
          input.val(input.attr('placeholder'))
      })
    })
  }
}

// Fix input placeholder issue for IE8 and IE9
export function fixPlaceholderForIE() {
  setTimeout(() => {
    _fixPlaceholderForIE()
  }, 600)
}

/**
 * 从 www.abc.com?id=1&name=soon 字符串中提取 对应key的值
 * @ paramName {String}
 * @ searchString {String}
 */
export function getURLParameter(paramName, searchString) {
  const str = searchString.split('?')[1]
  let i
  let val
  const params = str.split('&')

  for (i = 0; i < params.length; i++) {
    val = params[i].split('=')
    if (val[0] === paramName)
      return decodeURIComponent(val[1])
  }
  return null
}

/**
 * 将URL中query部分(即?后面的)的参数转化为对象，重复的参数作为数组。
 * 如果？后面没有参数返回空对象{}
 *
 *  样例一：
 *  parseQuery('http:www.baidu.com/index?name=username&city=%E5%8C%97%E4%BA%AC&id=123&id=456#flag=66')
 *  输出：
 *  {
 *   name: 'username',
 *   city: '北京', // 中文需解码
 *   id: [ 123, 456 ], // 重复出现的 key 要组装成数组，能被转成数字的就转成数字类型
 *  }
 *
 * 样例二：parseQuery('http://localhost:8000/#/demo/curd')  => {}
 *
 * @param {String} url
 */
export function parseQuery(url) {
  const result = {}
  const reg = /[?&]([^=&#]+)=([^&#]*)/g
  const querys = url.match(reg)
  if (querys) {
    for (let i = 0; i < querys.length; i++) {
      const query = querys[i].split('=')
      const key = query[0].substr(1)
      // js中最大安全数字为9007199254740991 （16位），超过时parseFloat()精度会丢失
      // 所以对于length <= 15的数字，才转，其余的当字符串
      const value
        = /^\d+$/.test(query[1]) && query[1].length <= 15
          ? parseFloat(query[1])
          : decodeURIComponent(query[1])
      if (result[key])
        result[key] = [].concat(result[key], value)

      else
        result[key] = value
    }
  }
  return result
}

/**
 * 获取视窗宽高
 * 关于视窗的各种尺寸可参考：
 * https://www.w3cplus.com/sites/default/files/blogs/2017/1707/vw-layout-4.png
 * @includeScollbar   {Boolean}  true为包含滚动条的宽度，false反之
 * @return            {Object}   返回一个包含width和height2个属性的对象。
 *                    width：浏览器视窗的宽度，height为窗口的高度
 */
export function getViewPort(includeScollbar) {
  const isInclude = includeScollbar || false
  if (isInclude) {
    let e = window
    let a = 'inner'
    if (!('innerWidth' in window)) {
      a = 'client'
      e = document.documentElement || document.body
    }
    return {
      width: e[`${a}Width`],
      height: e[`${a}Height`],
    }
  }
  const de = document.documentElement
  const db = document.body
  const viewW = de.clientWidth === 0 ? db.clientWidth : de.clientWidth
  const viewH = de.clientHeight === 0 ? db.clientHeight : de.clientHeight
  return {
    width: viewW,
    height: viewH,
  }
}

// "a=2&b=3&c=4" 序列化成 {a:2,b:3,c:4}
export function serialize(str) {
  // 修复 jquery.serialize() 会把空格转成'+'的坑
  const s = str.replace(/\+/g, ' ')
  const obj = {}
  const params = s.split('&')
  for (let i = 0; i < params.length; i++) {
    const val = params[i].split('=')
    // 多选的select，在jquery.serialize()的时候名称都是相同的，如右：rules=1&rules=3
    // 这个时候需要把值以数组的形式保存，如右：rules：[1,3]
    if (obj[val[0]]) {
      const arr = []
      arr.push(obj[val[0]]) // 读取已存在的，保存到临时数组
      arr.push(unescape(val[1]))
      obj[val[0]] = arr
    }
    else {
      obj[val[0]] = unescape(val[1])
    }
  }
  return obj
}

/**
 * encrypt 加密
 * https://github.com/brix/crypto-js#object-encryption
 * @param {any} value 任意合法值，object,string,array等
 * @param {string} secretKey 秘钥
 * @return {string} 字符串
 */
export function encrypt(value, secretKey) {
  return CryptoJS.AES.encrypt(JSON.stringify(value), secretKey).toString()
}

/**
 * encrypt 解密
 * https://github.com/brix/crypto-js#object-encryption
 * @param {string} ciphertext 由encrypt()加密后生成的字符串
 * @param {string} secretKey 秘钥
 * @return {any}
 */
export function decrypt(ciphertext, secretKey) {
  if (isJSON(ciphertext))
    return JSON.parse(ciphertext)

  const result = CryptoJS.AES.decrypt(ciphertext, secretKey)
  return JSON.parse(result.toString(CryptoJS.enc.Utf8))
}

/**
 * 本地存储 - 存
 * @param {string} key 必填
 * @param {any} value 必填
 * @param {string} mode ' 非必填，默认是sessionStorage。'sessionStorage | localStorage'
 */
export function setItem(key, value, mode = 'sessionStorage') {
  if (['sessionStorage', 'localStorage'].includes(mode)) {
    if (value !== undefined)
      window[mode].setItem(`${SESSION_PREFIX}${key}`, encrypt(value, key))
  }
}

/**
 * 本地存储 - 取
 * @param {string} key 必填
 * @param {string} mode  非必填，默认是sessionStorage。'sessionStorage | localStorage'
 */
export function getItem(key, mode = 'sessionStorage') {
  let result
  if (key && ['sessionStorage', 'localStorage'].includes(mode)) {
    result = window[mode].getItem(`${SESSION_PREFIX}${key}`)
    if (result !== null)
      return decrypt(result, key)

    return null
  }
  return null
}

// /**
//  * 本地存储 - 存
//  * @param {string} key 必填
//  * @param {any} value 必填
//  * @param {string} mode ' 非必填，默认是sessionStorage。'sessionStorage | localStorage'
//  */
// export function setItem(key, value, mode = 'sessionStorage') {
//   if (['sessionStorage', 'localStorage'].indexOf(mode) !== -1) {
//     if (value !== undefined) {
//       window[mode].setItem(`${SESSION_PREFIX}${key}`, JSON.stringify(value));
//     }
//   }
// }

// /**
//  * 本地存储 - 取
//  * @param {string} key 必填
//  * @param {string} mode  非必填，默认是sessionStorage。'sessionStorage | localStorage'
//  */
// export function getItem(key, mode = 'sessionStorage') {
//   let result;
//   if (key !== undefined && key !== '' && ['sessionStorage', 'localStorage'].indexOf(mode) !== -1) {
//     result = window[mode].getItem(`${SESSION_PREFIX}${key}`);
//     if (result !== null) {
//       return isJSON(result) ? JSON.parse(result) : result;
//     }
//     return null;
//   }
//   return false;
// }

/**
 * 本地存储 - 删
 * @param {string} key 必填
 * @param {string} mode  非必填，默认是sessionStorage。'sessionStorage | localStorage'
 */
export function removeItem(key, mode = 'sessionStorage') {
  if (['sessionStorage', 'localStorage'].includes(mode)) {
    if (key !== undefined)
      window[mode].removeItem(`${SESSION_PREFIX}${key}`)
  }
}

/**
 * 清除带项目前缀的storage
 * @param {string} mode  非必填，默认是sessionStorage。'sessionStorage | localStorage'
 */
export function storageClear(mode = 'sessionStorage') {
  if (['sessionStorage', 'localStorage'].includes(mode)) {
    const storage = window[mode]
    const targets = []
    for (let i = 0, len = storage.length; i < len; i++) {
      const key = storage.key(i)
      if (`${key}`.includes(`${SESSION_PREFIX}`))
        targets.push(key)
    }
    targets.forEach((value) => {
      window[mode].removeItem(value)
    })
  }
}

/**
 * 首字母转大写
 * @param {*} str
 */
export function firstUpperCase(str) {
  return str.replace(/( |^)[a-z]/g, L => L.toUpperCase())
}

/* eslint-disable */
/**
 * js密码加密函数，对应java端用com.ztesoft.bss.orgauth.util.JsPasswordUtil.decodePwd来解密.
 * @param pwd
 * @returns {*}
 */
export function jsEncodePwd(pwd) {
  var bytes = strToUtf8Bytes(pwd);
  return encodeBytes(bytes);
}

/**
 * 转化字节数组为字符串，每个字节的高4位和低4位+字符a组成2个ascii字符.
 * @param bytes
 * @returns {string}
 */
export function encodeBytes(bytes) {
  var re = [];
  for (var i = 0; i < bytes.length; i++) {
    var ch1 = String.fromCharCode(((bytes[i] >> 4) & 0xf) + 0x61);
    var ch2 = String.fromCharCode((bytes[i] & 0xf) + 0x61);
    re.push(ch1);
    re.push(ch2);
  }
  return re.join('');
}

/**
 * 字符串转化为utf8字节数组
 * @param str
 * @returns {Array}
 */
export function strToUtf8Bytes(str) {
  // TODO(user): Use native implementations if/when available
  var out = [],
    p = 0;
  for (var i = 0; i < str.length; i++) {
    var c = str.charCodeAt(i);
    if (c < 128) {
      out[p++] = c;
    } else if (c < 2048) {
      out[p++] = (c >> 6) | 192;
      out[p++] = (c & 63) | 128;
    } else if (
      (c & 0xfc00) == 0xd800 &&
      i + 1 < str.length &&
      (str.charCodeAt(i + 1) & 0xfc00) == 0xdc00
    ) {
      // Surrogate Pair
      c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff);
      out[p++] = (c >> 18) | 240;
      out[p++] = ((c >> 12) & 63) | 128;
      out[p++] = ((c >> 6) & 63) | 128;
      out[p++] = (c & 63) | 128;
    } else {
      out[p++] = (c >> 12) | 224;
      out[p++] = ((c >> 6) & 63) | 128;
      out[p++] = (c & 63) | 128;
    }
  }
  return out;
}

/**
 * https://github.com/leon-good-life/wheel-react
 * 监听鼠标滚轮
 */
export function mouseWheel() {
  const WheelReact = {};

  WheelReact.pauseWheelEvent = false;

  WheelReact._config = {};

  WheelReact.config = config => {
    const options = ['right', 'left', 'up', 'down'];
    for (const option of options) {
      if (config.hasOwnProperty(option)) {
        WheelReact._config[option] = config[option];
      }
    }
  };

  WheelReact.events = {
    onWheel: e => {
      if (WheelReact.pauseWheelEvent) {
        return;
      }
      if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
        if (e.deltaX > 0 && WheelReact._config.hasOwnProperty('left')) {
          WheelReact._config.left();
        } else if (e.deltaX < 0 && WheelReact._config.hasOwnProperty('right')) {
          WheelReact._config.right();
        }
      } else if (e.deltaY > 0 && WheelReact._config.hasOwnProperty('up')) {
        WheelReact._config.up();
      } else if (e.deltaY < 0 && WheelReact._config.hasOwnProperty('down')) {
        WheelReact._config.down();
      }
      WheelReact.pauseWheelEvent = true;
      WheelReact.timeout = setTimeout(() => {
        WheelReact.pauseWheelEvent = false;
      }, 200);
    },
  };

  WheelReact.clearTimeout = () => {
    if (WheelReact.timeout) {
      clearTimeout(WheelReact.timeout);
    }
  };
  return WheelReact;
}
/* eslint-enable */

// 获取终端类型
export function getTerminalType() {
  /**
   *终端类型-PC
   */
  const TERMINAL_TYPE_PC = '1000'
  /**
   *终端类型-PAD
   */
  const TERMINAL_TYPE_PAD = '1100'
  /**
   *终端类型-MOBILE
   */
  const TERMINAL_TYPE_MOBILE = '1200'
  const u = navigator.userAgent
  // 移动终端浏览器版本信息
  const agent = {
    trident: u.includes('Trident'), // IE内核
    presto: u.includes('Presto'), // opera内核
    webKit: u.includes('AppleWebKit'), // 苹果、谷歌内核
    gecko: u.includes('Gecko') && !u.includes('KHTML'), // 火狐内核
    mobile: !!u.match(/AppleWebKit.*Mobile.*/) || !!u.match(/AppleWebKit/), // 是否为移动终端
    ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), // ios终端
    android: u.includes('Android') || u.includes('Linux'), // android终端或者uc浏览器
    iPhone: u.includes('iPhone'), //  || u.indexOf('Mac') > -1  是否为iPhone或者QQHD浏览器
    iPad: u.includes('iPad'), // 是否iPad
    google: u.includes('Chrome'),
  }

  if (agent.iPad)
    return TERMINAL_TYPE_PAD

  if (agent.android || agent.iPhone)
    return TERMINAL_TYPE_MOBILE

  return TERMINAL_TYPE_PC
}

/**
 * 根据提供的card高度，计算card-body内最多可以放多少行，行高定位33
 * 38 是.ant-card-head高度
 * 32 是ant-card-body上下内边距的高度
 * 33 是thead高度
 * 40 是table-pagination.mini高度
 * @param {number} card  card高度
 */
export function getPageSizeByCardHeight(height, lineHeight) {
  let lineH
  if (typeof lineHeight === 'number')
    lineH = lineHeight

  else
    lineH = 33

  return Math.floor((height - 38 - 32 - 33 - 40) / lineH)
}

/**
 * 根据url中是否存在viewMode来判断当前视图模式
 *  - url中必须同时包含viewMode, bss3SessionId两个有效字段才会返回'inner'
 * @return {string} 返回 "normal | inner"; normal表示有header和tab-nav; inner相反
 */
export function getViewMode() {
  const { layout } = parseQuery(window.location.href)

  if (typeof layout === 'string' && layout.toLocaleLowerCase() === 'inner')
    return 'inner'

  return 'normal'
}

/**
 * var actionLog = {
 * actionType: ACTION_TYPE_FUNC,   动作类型
 * actionModule: self.menuId,    菜单id
 * action: ACTION_PWD,  对应动作
 * handResult：RESULT_SUCCESS  操作结果
 * actionMsg： msg     错误信息（操作失败时有值）
 * 记录菜单日志和按钮操作日志
 */
export function operActionLog(params) {
  request('orgauth/SystemActionLogController/collectSystemActionLog.do', { data: params })
}

/**
 * 任何int%1 都是0，而float%1 就不等于0
 * @param {*} val
 */
export function isFloat(val) {
  return parseFloat(val) % 1 !== 0
}

/**
 *
 * @param {string} pathname 如：/notice 首字母必须带斜杆
 */
export const Portal = {
  close: pathname =>
    window.top.postMessage(
      JSON.stringify({
        to: 'portal',
        action: 'close',
        pathname,
      }),
      '*',
    ),
  open: pathname =>
    window.top.postMessage(
      JSON.stringify({
        to: 'portal',
        action: 'open',
        pathname,
      }),
      '*',
    ),
}

export function getUniqueID(prefix) {
  if (prefix) {
    // eslint-disable-next-line prefer-template
    return prefix + '_' + Math.floor(Math.random() * new Date().getTime())
  }
  return Math.floor(Math.random() * new Date().getTime())
}

// 类似jquery的closest（）
/**
 *
 * @param {*} el dom元素
 * @param {*} selector 选择器 如：'.container' '#id'
 */
export function closest(el, selector) {
  const matchesSelector
    = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector

  while (el) {
    if (matchesSelector.call(el, selector))
      break

    el = el.parentElement
  }
  return el
}

export function isEmptyObj(obj) {
  if (obj === undefined || Object.keys(obj).length === 0)
    return true

  return false
}

/**
  * 返回树状结构中，某个节点的所有父节点
  * const tree = [{
    menuName: '5G专网业务包',
    menuId: 1,
    menuCode: '5GZWYWB',
    children: [
      {
        menuName: '资源勘查',
        menuId: 11,
        menuCode: 'ZYKC',
        urlAddr: '/5g/zykc/123/456',
      },
      {
        menuName: '切片开通',
        menuId: 12,
        menuCode: 'QPKT',
        urlAddr: '/5g/qpkt',
      },
    ],
  }]

  findPath(tree, item => menuId === 11)  => [object,object]
 * @param {object[]} tree 对象数组

 * @param {function} func 断言函数
 * @param {*} path
 * @returns
 */
export function findPath(tree, func, path = []) {
  if (!Array.isArray(tree))
    return []

  for (let i = 0; i < tree.length; i++) {
    // 这里按照你的需求来存放最后返回的内容吧
    path.push(tree[i])

    if (func(tree[i]))
      return path

    if (tree[i].children) {
      const findChildren = findPath(tree[i].children, func, path)

      if (findChildren.length)
        return findChildren
    }

    path.pop()
  }

  return []
}

// children 是数组且非空
export function hasChildren(node) {
  const r = get(node, 'children')
  return r && Array.isArray(r) && r.length > 0
}

/**
 * 遍历每个节点，并执行回调，最后返回新的树（不会改变原tree）
 * 注意：
 * 1、第三、四个参数均为非必需
 * 2、接收三个参数时：如果第三个参数是array，则表示初始节点的所有父节点; 如果是`string`则表示 重置`childrenField`
 * 3、接收四个参数时：一次表示``parents`和`childrenField`
 * @param {object} tree
 * @param {function} cb
 * @param {array | string} _currentNodeParents 非必选，不写默认是[]，表示每个节点的所有父节点，并作为回调函数`cb`的第二个参数。
 * @param {string} _childrenField 非必选，默认是'children'，表示子节点的key。
 * @returns 返回新的树（不会改变原tree）
 */
export function mapNodes(nodes, cb, _currentNodeParents, _childrenField) {
  if (!Array.isArray(nodes))
    throw new Error('Error! The first argument must be a array')

  if (typeof cb !== 'function')
    throw new Error('Error! The second argument must be a function')

  nodes = nodes.filter(Boolean)

  let parents = []
  let childrenField = 'children'

  if (Array.isArray(_currentNodeParents)) {
    parents = _currentNodeParents
    childrenField = typeof _childrenField === 'string' ? _childrenField : childrenField
  }

  if (typeof _currentNodeParents === 'string') {
    parents = []
    childrenField = _currentNodeParents
  }

  function mapNode(node, cb, parents = [], parent) {
    const mappedNode = cb(node, parents, parent)

    if (!mappedNode)
      return mappedNode

    if (hasChildren(node)) {
      const children = []
      node[childrenField].forEach((n) => {
        const child = mapNode(n, cb, [...parents, mappedNode], mappedNode)
        if (child)
          children.push(child)
      })

      return { ...mappedNode, [childrenField]: children }
    }
    return mappedNode
  }

  const result = []
  nodes.forEach((node) => {
    const n = mapNode(node, cb, parents, null)
    if (n)
      result.push(n)
  })
  return result
}

/**
 * 遍历树并过滤出符合断言函数的所有节点，返回一个新的树
 * 注意：
 * 1、第三、四个参数均为非必需
 * 2、接收三个参数时：如果第三个参数是array，则表示初始节点的所有父节点; 如果是`string`则表示 重置`childrenField`
 * 3、接收四个参数时：一次表示``parents`和`childrenField`
 * @param {object[]} nodes 节点树
 * @param {func} predicate 断言函数
 * @param {array | string} _currentNodeParents 非必选，不写默认是[]，表示每个节点的所有父节点，并作为回调函数`cb`的第二个参数。
 * @param {string} _childrenField 非必选，默认是'children'，表示子节点的key。
 * @returns 新的节点树
 */
export function filterNodes(nodes, predicate, _currentNodeParents, _childrenField) {
  let parents = []
  let childrenField = 'children'

  if (Array.isArray(_currentNodeParents)) {
    parents = _currentNodeParents
    childrenField = typeof _childrenField === 'string' ? _childrenField : childrenField
  }

  if (typeof _currentNodeParents === 'string') {
    parents = []
    childrenField = _currentNodeParents
  }

  function isBranch(nodeData) {
    const children = nodeData && nodeData[childrenField]
    return children && children.length >= 0
  }

  function filterNode(node, predicate, parents = []) {
    let res = null

    const filteredChildren = isBranch(node)
      ? node[childrenField]
        .map(childNode => filterNode(childNode, predicate, [...parents, node]))
        .filter(i => i !== null)
      : null

    const hasChildrenMatched = filteredChildren && filteredChildren.length > 0
    const isNodeItselfMatched = predicate(node, parents)

    if (isNodeItselfMatched || hasChildrenMatched) {
      const childrenData = filteredChildren ? { [childrenField]: filteredChildren } : {}
      res = Object.assign({}, node, childrenData)
    }

    return res
  }

  return nodes.map(node => filterNode(node, predicate, parents)).filter(i => i !== null)
}

// layout中 content部分的高度
export function getHeight() {
  const { height } = getViewPort(false)
  const $header = document.getElementById('layout-header')
  const $tabsNav = document.querySelector('.layout-tabs-nav')

  return (
    height
    - ($header === null ? 0 : $header.offsetHeight)
    - ($tabsNav === null ? 0 : $tabsNav.offsetHeight)
    - 8 * 2
  )
}

export function format(str) {
  if (str === undefined || str === null)
    return str

  if (isJSON(str))
    return formatJSON(str)

  if (trimStart(str).startsWith('<'))
    return formatXML(str)

  return str
}

window.getHeight = getHeight

export function getChildByType(children, type) {
  return React.Children.toArray(children).find(child => child.type.name === type) || React.Fragment
}

export function hasEventListener(element, eventType) {
  const eventListeners = window.getEventListeners(element)
  return eventListeners.hasOwnProperty(eventType) && eventListeners[eventType].length > 0
}

export function uuid() {
  const timestamp = Date.now()
  const randomChars = Math.random().toString(36).substring(2, 8) // 使用随机字符生成唯一标识
  return timestamp + randomChars
}

export function exportToCsv(columns, data, filename) {
  let content = ''
  const header = columns.map(col => col.title).join(',')
  content += `${header}\r\n`

  data.forEach((row) => {
    const line = columns.map(col => `"'${row[col.dataIndex]}"`).join(',')
    content += `${line}\r\n`
  })

  const blob = new Blob([content], { type: 'text/plain;charset=utf-8' })
  FileSaver.saveAs(blob, `${filename}.csv`)
}
